# -*- coding: utf-8 -*-

import logging
import traceback

from flask import current_app, has_request_context, _request_ctx_stack, abort
from werkzeug.utils import import_string
from werkzeug.local import LocalProxy
from itsdangerous import TimedJSONWebSignatureSerializer, SignatureExpired, BadSignature
from flask_restful import reqparse

from mongoengine import errors as mongo_errors

from weixin import WXAPPAPI
from weixin.lib.wxcrypt import WXBizDataCrypt

__all__ = ['Auth', 'current_user']

logger = logging.getLogger(__name__)

current_user = LocalProxy(lambda: _get_user())


class Auth(object):
    def __init__(self, app=None):
        self.app = None
        self.wx_api = None
        self.session_expired = None
        self.secret_key = None

        self.parser = reqparse.RequestParser()
        self.parser.add_argument(
            'Authorization',
            location='headers',
            dest='token',
            required=True,
            help='Missing session token data'
        )

        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        appid = app.config['WXAPP_ID']
        app_secret = app.config['WXAPP_SECRET']
        self.secret_key = app.config['SECRET_KEY']
        self.session_expired = app.config.setdefault('SESSION_EXPIRED', 60 * 60 * 24 * 7)
        logger.info('Set serializer expire time: %s', self.session_expired)

        self.serializer = TimedJSONWebSignatureSerializer(
            self.secret_key,
            self.session_expired
        )

        self.user_cls = import_string(
            app.config.setdefault('AUTH_USER_CLASS', 'ins.models:User')
        )

        self.wx_api = WXAPPAPI(appid=appid, app_secret=app_secret)

        app.auth = self
        self.app = app

    def get_session_info(self, code):
        try:
            session_info = self.wx_api.exchange_code_for_session_key(code=code)
        except:
            logger.error(
                'Exchange session key FAILED. %s. Error: %s',
                code,
                traceback.format_exc()
            )
            session_info = None
            raise
        else:
            logger.debug('Retrieved user info %s -> %s', code, session_info)
        return session_info

    def login(self, code):
        session_info = self.get_session_info(code)
        user = self.user_cls.login(session_info)
        token = self.serializer.dumps({'uid': str(user.pk)})
        # session = self.serializer.dumps({'uid': code})
        return token, user.openid

    def verify_session_token(self, token):
        try:
            data = self.serializer.loads(token)
        except SignatureExpired:
            logger.info(
                'Token EXPIRED.',
            )
            return False, 'Token EXPIRED'
        except BadSignature:
            logger.info('Token data error: %s', token)
            return False, 'Bad token signature'
        return self.get_user_from_token(data)

    def get_user_from_token(self, data):
        try:
            user = self.user_cls.objects.get(pk=data['uid'])
        except mongo_errors.DoesNotExist:
            logger.error('User not exists: %s -> %s', data['uid'])
            return False, 'User not exists'
        except:
            logger.error(
                'Error occurred during getting user from token. Error: %s',
                traceback.format_exc()
            )
            return False, 'Retrieve user from token failed'
        return True, user


def _get_user():
    if has_request_context() and hasattr(_request_ctx_stack.top, 'user'):
        return getattr(_request_ctx_stack.top, 'user')

    try:
        args = current_app.auth.parser.parse_args()
    except:
        anonymous = current_app.auth.user_cls.get_anonymous()
        setattr(_request_ctx_stack.top, 'user', anonymous)
        return anonymous
    token = args['token']
    try:
        bearer, token = token.split(' ')
        logger.debug('Retrieved ser token from headers: %s', token)
    except:
        logger.info(
            'Token format error: %s Error: %s',
            token,
            traceback.format_exc()
        )
        abort(403, 'Token format error')
    try:
        success, user_or_msg = current_app.auth.verify_session_token(token)
    except:
        logger.error(
            'Error occurred during verifying session token. Error: %s',
            traceback.format_exc()
        )
        abort(403, 'Token verifying error')
    if not success:
        abort(401, user_or_msg)

    setattr(_request_ctx_stack.top, 'user', user_or_msg)
    return user_or_msg
